package com.snail.generator.utils;

import com.snail.generator.common.WebContext;
import com.snail.generator.entity.ColumnInfo;
import com.snail.generator.entity.GenParams;
import com.snail.generator.entity.TableInfo;
import com.snail.generator.mapper.GeneratorMapper;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.util.StringUtils;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Slf4j
public class GenUtils {

    @SneakyThrows
    public static byte[] generateCode(String packages, String module, String tablePrefix, List<TableInfo> tableInfos) {
        GeneratorMapper generatorMapper = WebContext.getGeneratorMapper();
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ZipOutputStream zip = new ZipOutputStream(outputStream);
        Properties properties = PropertiesLoaderUtils.loadProperties(new ClassPathResource("generator.properties"));

        Map<String, String> allFiles = getFileContents();
        VelocityEngine velocityEngine = new VelocityEngine();
        velocityEngine.init();

        for (TableInfo table : tableInfos) {
            // 获取列信息
            List<ColumnInfo> columnInfos = generatorMapper.queryColumn(table.tableName);

            GenParams genParams = new GenParams();
            genParams.packages = packages;
            genParams.packageName = String.join(File.separator, packages.split("\\.")) + File.separator + module;
            genParams.module = module;
            genParams.tablePrefix = tablePrefix;
            genParams.tableName = table.tableName;
            genParams.tableComment = table.tableComment;
            genParams.class_name = tablePrefix == null ? table.tableName : table.tableName.startsWith(tablePrefix) ? table.tableName.substring(tablePrefix.length()) : table.tableName;
            genParams.ClassName = tableNameToClassName(genParams.class_name);
            genParams.className = StringUtils.uncapitalize(genParams.ClassName);
            genParams.columns = columnInfos;

            Set<String> importPackages = new HashSet<>();
            List<ColumnInfo> columnPks = new ArrayList<>(); // 主键，可能为联合主键（）
            for (ColumnInfo columnInfo : columnInfos) {
                columnInfo.fieldName = StringUtils.uncapitalize(tableNameToClassName(columnInfo.columnName));
                String fieldType = properties.getProperty(columnInfo.columnType.toLowerCase());
                if (fieldType == null) throw new RuntimeException("未知的 sql 类型：" + columnInfo.columnType);
                columnInfo.fieldType = fieldType;
                columnInfo.tsType = getTsType(columnInfo.fieldType);

                // 获取主键
                if ("PRI".equalsIgnoreCase(columnInfo.columnKey))
                    columnPks.add(columnInfo);

                if ("BigDecimal".equalsIgnoreCase(columnInfo.fieldType))
                    importPackages.add("import java.math.BigDecimal;");

                if ("Date".equalsIgnoreCase(columnInfo.fieldType))
                    importPackages.add("import java.util.Date;");

                if ("LocalDateTime".equalsIgnoreCase(columnInfo.fieldType))
                    importPackages.add("import java.time.LocalDateTime;");

                if ("LocalDate".equalsIgnoreCase(columnInfo.fieldType))
                    importPackages.add("import java.time.LocalDate;");

                if ("LocalTime".equalsIgnoreCase(columnInfo.fieldType))
                    importPackages.add("import java.time.LocalTime;");
            }
            genParams.columns.removeIf(columnPks::contains); // 移除主键特殊处理

            Map<String, Object> params = genParams.toMap();
            params.put("fieldPk", columnPks);
            params.put("importPackages", importPackages);

            VelocityContext context = new VelocityContext(params);

            allFiles.forEach((filePath, fileContent) -> {
                if (!filePath.endsWith(".vm")) return;

                filePath = processTemplate(filePath.substring(0, filePath.length() - 3), params);

                StringWriter sw = new StringWriter();
                velocityEngine.evaluate(context, sw, "logTag", fileContent);

                try {
                    zip.putNextEntry(new ZipEntry(filePath));

                    IOUtils.write(sw.toString(), zip, "UTF-8");
                    IOUtils.closeQuietly(sw);
                    zip.closeEntry();
                } catch (IOException e) {
                    throw new RuntimeException("渲染模板失败，表名：" + table.tableName, e);
                }
            });
        }
        IOUtils.closeQuietly(zip);
        return outputStream.toByteArray();
    }

    private static String getTsType(String fieldType) {
        return switch (fieldType) {
            case "Integer" -> "number";
            case "Boolean" -> "boolean";
            default -> "string";
        };
    }

    public static String tableNameToClassName(String tableName) {
        String[] parts = tableName.split("_");
        StringBuilder className = new StringBuilder();

        for (String part : parts) {
            className.append(part.substring(0, 1).toUpperCase()).append(part.substring(1).toLowerCase());
        }

        return className.toString();
    }

    private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\$\\{([^}]*)}");

    private static String processTemplate(String tempName, Map<String, Object> params) {
        Matcher matcher = VARIABLE_PATTERN.matcher(tempName);
        StringBuilder sb = new StringBuilder();

        while (matcher.find()) {
            String variableName = matcher.group(1); // 提取变量名
            Object replacement = params.get(variableName);
            if (replacement == null) throw new RuntimeException("未提供参数：" + variableName);

            matcher.appendReplacement(sb, replacement.toString().replace("\\", "\\\\"));
        }
        matcher.appendTail(sb); // 添加剩余的部分

        return sb.toString();
    }

    @SneakyThrows
    public static Map<String, String> getFileContents() {
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Path templateDir = Path.of(resolver.getResource("/templates").getURI());
        Resource[] resources = resolver.getResources("/templates/**");

        Map<String, String> files = new HashMap<>();
        for (Resource resource : resources) {
            Path path = Path.of(resource.getURI());
            if (Files.isRegularFile(path)) {
                String filePath = templateDir.relativize(path).toString();
                String fileContent = Files.readString(path);
                files.put(filePath, fileContent);
            }
        }
        return files;
    }
}
